#ifndef _GLWINDOW_H
#define _GLWINDOW_H


#include <FL/Fl.H>
#include <FL/Fl_Gl_Window.H>
#include <FL/gl.h>
#include <cmath>
#include <algorithm>
#include <vector> 
#include <cassert>
#include <fstream>

#include "Waveform.h"
#include "AudioPlayer.h"


//Helper struct to manage selecting ranges of the visible waveform.
struct Selection {
  float s1, s2;
  int   c;
  
  // convoluted way to make assign upper&lower limits
  void  set(float s) {
    float* big;
    float* small;
    if( s1 >= s2 ) { big = &s1; small = &s2; } 
    else { big = &s2; small = &s1; }

    if( s >= *big || s > (*big+*small)/2 && *small != -1)
      *big = s; 
    if( s <= *small || *small == -1 || s < (*big + *small)/2)
      *small = s;
  }

  float getLeft(void)   { return std::min(s1, s2); }
  float getRight(void)  { return std::max(s1, s2); }

  Selection(void)       {setInvalid();}
  void setInvalid(void) {s1 = -1; s2 = -1; c = 0;}
  bool isValid(void)    { if(s1 != -1 && s2 != -1)return true; else return false;}
  
};



class GlWindow : public Fl_Gl_Window { 
  Waveform* mWfm; //pointer to the waveform - shared with the parent class.
  int mDragLeft; // this needs to be renamed.
  
 public:
  // this must be public so we can use it in the gui class to do insertions
  Selection mSelect;

 private:
  
  void draw() {
    //printf("width: %d, height: %d\n", w(), h());
    if (!valid()) {
      valid(1);
      glMatrixMode (GL_PROJECTION);
      glLoadIdentity ();
      glOrtho (0, w(), -h()/2, h()/2, 0, 1);
      glMatrixMode (GL_MODELVIEW);
      glDisable(GL_DEPTH_TEST);
    }

    glClear(GL_COLOR_BUFFER_BIT);

    // draw the background
    glColor3f(0.4297, 0.9023, 0.4297); //green
    glRectf(0, h(), w(), -h());
    
    if(mSelect.isValid()) {
      printf("drawing selection\n");
      printf("left: %f, right: %f\n", mSelect.getLeft(), mSelect.getRight());
      // draw the selection box (if any)
      glColor3f(0.7, 0.746, 0.0625); // orange
      //glRectf(0.5*w(), 1.0*h(), 0.75*w(), -1.0*h());
      glRectf(mSelect.getLeft() * w(), h(), mSelect.getRight() * w(), -h());
    }

    //line across the center of the screen.
    glColor3f(0.6797, 0.8672, 0.9062); //light blue
    glBegin(GL_LINE);
    glVertex2f(0, 0);
    glVertex2f(w(), 0);
    glEnd(); 
    
    //printf("waveform size: %d", mWfm->size());
    //draw the waveform
    glColor3f(0.0, 0.0, 0.0);
    glBegin(GL_LINE_STRIP);
    for ( unsigned i=0; i<mWfm->size(); i++ ) {
      glVertex2f( ((float)i/(mWfm->size()-1)) * w() , (*mWfm)[i]*h() );
    }
    glEnd();

  }
  
  int handle( int event ) {
    switch ( event ) {
    case FL_DRAG:
    case FL_PUSH: {
      if( Fl::event_button() == 1 ) {
	//printf("Left mouse button pressed.\n");
	
	Waveform& v = *mWfm;
	
	float mouseX = (float) Fl::event_x();
	float mouseY = (float) Fl::event_y();

	//printf("x: %f, y: %f\n", mouseX, mouseY);

	float idx =  mouseX / w() * v.size();
	float pdx =  (mouseX - 1) / w() * v.size();
	float ndx =  (mouseX + 1) / w() * v.size();
	//printf("p: %f, i: %f, n: %f\n", pdx, idx, ndx);
	
	//don't try to index a point that doesn't exit
	if( floor(idx) < 0 || floor(idx) > v.size()+1 ) { return 1; }

	//assign this click to the proper point,
	//but normalize it so it makes sense when we resize
	int choose = (int)floor(idx);
	float val  = -(mouseY/h() - .5);
	
	// confine the y-value to within the screen.
	if( val >  0.5 ) val =  0.5;
	if( val < -0.5 ) val = -0.5;

	// put the 'value' in the point we've selected.
	v[choose] = val;
	
	idx = floor(idx); ndx = floor(ndx); pdx = floor(pdx);
	//printf("pdx: %f\n", pdx);

        /******************* Smoothing 'Missed' Points ************************/
	// don't 'skip' points when we're dragging
	if( mDragLeft != -1 && abs(mDragLeft-choose) > 1 ) {
	  
	  switch ( 1 ) {
	    // rough  - no interpolation
	  case 0: {
	    int inc = (mDragLeft<choose)?1:-1;
	    for( int iidx = inc;  mDragLeft+iidx != choose; iidx+=inc ) {
	      v[mDragLeft+iidx] = val;
	    }
	    break;
	  }
	    // linear
	  case 1: {
	    int inc = (mDragLeft<choose)?1:-1; 
	    for( int iidx = inc;  mDragLeft+iidx != choose; iidx+=inc ) {
	      //printf("index: %d; length: %d\n", (int)mDragLeft, (int)v.size());
              v[mDragLeft+iidx] = v[mDragLeft] + iidx * (v[choose] - v[mDragLeft])/(choose-mDragLeft);
            }        
	    break;
	  }
	    // smooth
	  case 2: {
	    printf("smoothing not yet implemented.");
	    break;
	  }
	  default:
	    printf("Not doing any kind of smoothing...poop\n");    
	  }
	} 
	/*******************************************/
	
	// now remember the current point as the last one we've updated.
	mDragLeft = (int)floor(idx);
	redraw();
	return 1;
      } 
      if( Fl::event_button() == 3 ) {
	printf("Right-click.\n");
       
	// if we double-click, reset selection
	if(  Fl::event_clicks() >= 1 ) {
	  printf("# of clicks: %d\n", Fl::event_clicks());
	  mSelect.setInvalid();
	} 
	// otherwise, let's add this point to our selection
	else {
	  float mouseX = (float) Fl::event_x();
	  float mouseY = (float) Fl::event_y();
	  
	  float idx =  floor(mouseX / w() * (*mWfm).size());
	  //don't try to index a point that doesn't exit
	  if( floor(idx) < 0 || floor(idx) > (*mWfm).size()+1 ) { return 1; }

	  mSelect.set(mouseX/w());
	  
	}
	redraw();
	return 1;
      }
    }
    case FL_RELEASE: {
      // forget about having pressed a previous point if we release the mouse.
      mDragLeft = -1;
      return 0;
    }
    default:
      return Fl_Gl_Window::handle(event);
    }
  }

  void resize(int X,int Y,int W,int H) {
    // not sure if this code is correct since I put height on +/-h()/2
    Fl_Gl_Window::resize(X,Y,W,H);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    glViewport(0,0,W,H);
    glOrtho (0, W, -H/2, H/2, 0, 1);
    glMatrixMode (GL_MODELVIEW);
    glDisable(GL_DEPTH_TEST);
    printf("resizing!\n");
    redraw();
  }

 public:
  
  ~GlWindow( void ) {
    invalidate();
    hide();
  }
  
 GlWindow(int X,int Y,int W,int H,Waveform* wfm) : Fl_Gl_Window(X,Y,W,H) {
    mWfm = wfm;
    end();
  }

};

#endif  //GLWINDOW_H
